﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Web.Caching;
using Amazon.SimpleDB;
using Amazon.SimpleDB.Model;
using NLog;
using ScrappyDB;
using ScrappyDB.BaseClasses;
using ScrappyDB.Utilities;

namespace ScrappyDB.MockData
{
    public class MockDB :IDb
    {
        private static readonly Logger Log = LogManager.GetCurrentClassLogger();

        #region "Mock data queues"

        private Queue<int> _countResult;
        private Queue<object> _loadResult;
        private Queue<object> _queryResult;
        private Queue<object> _saveCollectionResult;
        private Queue<object> _saveEntityResult;
        private Queue<string> _valueResult;

        #endregion

        #region IDb Members

        public Cache WebCache { get; set; }

        public SdbCollection<T> Query<T>() where T : class, new()
        {
            return InternalQuery<T>(null);
        }

        private SdbCollection<T> InternalQuery<T>(string query) where T : class, new()
        {
            Log.Info("MockDB.Query<{1}> - query: {0}", query, GetTypeName<T>());
            return GetQueryResult<T>();
        }



        public SdbCollection<T> Query<T>(string query) where T : class, new()
        {
            return InternalQuery<T>(query);
        }

        public SdbCollection<T> Query<T>(string query, params object[] args) where T : class, new()
        {
            return InternalQuery<T>(query);
        }

        public SdbCollection<T> Query<T>(string query, string nextToken) where T : class, new()
        {
            return InternalQuery<T>(query);
        }

        public SdbCollection<T> Query<T>(string query, string nextToken, params object[] args) where T : class, new()
        {
            return InternalQuery<T>(query);
        }

        public SdbCollection<T> Query<T>(string query, string nextToken, int startPosition, int rowsToReturn)
            where T : class, new()
        {
            return InternalQuery<T>(query);
        }

        public SdbCollection<T> Query<T>(string query, string nextToken, int startPosition, int rowsToReturn, params object[] args) where T : class, new()
        {
            return InternalQuery<T>(query);
        }

        public SdbCollection<T> Query<T>(bool consistentRead) where T : class, new()
        {
            return InternalQuery<T>(null);
        }

        public SdbCollection<T> Query<T>(string query, bool consistentRead) where T : class, new()
        {
            return InternalQuery<T>(query);
        }

        public SdbCollection<T> Query<T>(string query, bool consistentRead, params object[] args) where T : class, new()
        {
            return InternalQuery<T>(query);
        }

        public SdbCollection<T> Query<T>(string query, string nextToken, bool consistentRead)
            where T : class, new()
        {
            return InternalQuery<T>(query);
        }

        public SdbCollection<T> Query<T>(string query, string nextToken, bool consistentRead, params object[] args) where T : class, new()
        {
            return InternalQuery<T>(query);
        }

        public SdbCollection<T> Query<T>(string query, string nextToken, int startPosition, int rowsToReturn,
                                               bool consistentRead) where T : class, new()
        {
            return InternalQuery<T>(query);
        }

        public string QueryValue(string query)
        {
            return InternalQueryValue(query);
        }

        public string QueryValue(string query, bool consistentRead)
        {
            return InternalQueryValue(query);
        }

        private string InternalQueryValue(string query)
        {
            Log.Info("MockDB.QueryValue - query: {0}", query);
            return GetValueResult();
        }
        public Collection<T> QueryByExample<T>(T o) where T : class, new()
        {
            throw new NotImplementedException();
        }

        public T Find<T>(object id) where T : class, new()
        {
            return InternalLoad<T>(id.ToString());
        }
        
        public T Find<T>(Guid id) where T : class, new()
        {
            return InternalLoad<T>(id.ToString());
        }

        public T Find<T>(DateTime id) where T : class, new()
        {
            return InternalLoad<T>(id.ToString());
        }

        public T Find<T>(int id) where T : class, new()
        {
            return InternalLoad<T>(id.ToString());
        }

        public T Find<T>(string id) where T : class, new()
        {
            return InternalLoad<T>(id);
        }

        public T Find<T>(object id, bool consistentRead) where T : class, new()
        {
            return InternalLoad<T>(id.ToString());
        }

        public SdbCollection<T> Find<T>(params object[] id) where T : class, new()
        {
            throw new NotImplementedException();
        }

        public SdbCollection<T> Find<T>(bool consistentRead, params object[] id) where T : class, new()
        {
            throw new NotImplementedException();
        }

        public T Find<T>(Guid id, bool consistentRead) where T : class, new()
        {
            return InternalLoad<T>(id.ToString());
        }

        public T Find<T>(DateTime id, bool consistentRead) where T : class, new()
        {
            return InternalLoad<T>(id.ToString());
        }

        public T Find<T>(int id, bool consistentRead) where T : class, new()
        {
            return InternalLoad<T>(id.ToString());
        }

        public T Find<T>(string id, bool consistentRead) where T : class, new()
        {
            return InternalLoad<T>(id);
        }

        public SdbCollection<T> Find<T>(IList<string> id) where T : class, new()
        {
            return InternalQueryLoad<T>(id.ToString());
        }

        public SdbCollection<T> Find<T>(IList<string> id, bool consistentRead) where T : class, new()
        {
            return InternalQueryLoad<T>(id.ToString());
        }

        private T InternalLoad<T>(string id) where T : class, new()
        {
            Log.Info("MockDB.Find<{1}> - id: {0}", id, GetTypeName<T>());

            return GetLoadResult<T>();
        }

        private string GetTypeName<T>() where T : new()
        {
            var o = new T();
            return o.GetType().Name;
        }

        private SdbCollection<T> InternalQueryLoad<T>(string id) where T : class, new()
        {
            Log.Info("MockDB.Find<{1}> - id: {0}", id, GetTypeName<T>());

            return GetQueryResult<T>();
        }

        public int Count<T>() where T : class, new()
        {
            return InternalCount<T>(null);
        }

        private int InternalCount<T>(string query) where T : class, new()
        {
            Log.Info("MockDB.Count<{1}> - query: {0}", query, GetTypeName<T>());
            return GetCountResult();
        }

        public int Count<T>(string query) where T : class, new()
        {
            return InternalCount<T>(query);
        }

        public int Count<T>(string query, string nextToken) where T : class, new()
        {
            return InternalCount<T>(query);
        }

        public int Count<T>(string query, string nextToken, int startPosition, int rowsToReturn)
            where T : class, new()
        {
            return InternalCount<T>(query);
        }

        public int Count<T>(bool consistentRead) where T : class, new()
        {
            return InternalCount<T>(null);
        }

        public int Count<T>(string query, bool consistentRead) where T : class, new()
        {
            return InternalCount<T>(query);
        }

        public int Count<T>(string query, string nextToken, bool consistentRead) where T : class, new()
        {
            return InternalCount<T>(query);
        }

        public int Count<T>(string query, string nextToken, int startPosition, int rowsToReturn, bool consistentRead)
            where T : class, new()
        {
            return InternalCount<T>(query);
        }

        public Collection<T> CountByExample<T>(T o) where T : class, new()
        {
            throw new NotImplementedException();
        }

        public void SaveChanges<T>(T actual) where T : class, new()
        {
            //actual.db = this;

            Log.Info("MockDB.SaveChanges<{0}>", GetTypeName<T>());
            if (_saveEntityResult == null)
                throw new MockDbExceptions.MockDbNoMockDataException();

            object test = _saveEntityResult.Peek();
            if (test.GetType() != actual.GetType())
                throw new MockDbExceptions.MockDbDataTypeMismatchException(
                    "Actual value saved was not of the same type as the expected value");

            var expected = (T)_saveEntityResult.Dequeue();

            var equal = Compare.CompareEntities(expected, actual);

            if (!equal)
                throw new MockDbExceptions.MockDbDataValueMismatchException("Saved values did not match expected values");
        }

        public BatchPutAttributesRequest CreateBatchRequest<T>(T entity) where T : class, new()
        {
            Log.Debug("MockDB.CreateBatchRequest<{0}>", GetTypeName<T>());
            throw new NotImplementedException();
        }

        public ReplaceableItem CreateBatchRequestItem<T>(T entity) where T : class, new()
        {
            Log.Debug("MockDB.CreateBatchRequestItem<{0}>", GetTypeName<T>());
            throw new NotImplementedException();
        }

        public void BatchPut(BatchPutAttributesRequest batchRequest)
        {
            throw new NotImplementedException();
        }

        public void BatchPut(BatchPutAttributesRequest batchRequest,AsyncCallback callback)
        {
            Log.Info("MockDB.BatchPutAttributesRequest<T>");
            throw new NotImplementedException();
        }

        public void Delete<T>(T entity) where T : class, new()
        {
            Log.Warn("Unverified call to MockDB.Delete<{0}>", GetTypeName<T>());
        }

        public void Delete<T>(IEnumerable<T> entities) where T : class, new()
        {
            Log.Warn("Unverified call to MockDB.Delete<{0}>", GetTypeName<T>());
        }

        public void Delete<T>(params object[] ids) where T : class, new()
        {
            Log.Warn("Unverified call to MockDB.Delete<{0}>", GetTypeName<T>());
        }


        public T DeleteOrphanProperties<T>(Guid id) where T : class, new()
        {
            throw new NotImplementedException();
        }

        public T DeleteOrphanProperties<T>(string id) where T : class, new()
        {
            throw new NotImplementedException();
        }

        public AmazonSimpleDB Service
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }

        public string EntityCacheKey(string domainName, string id)
        {
            throw new NotImplementedException();
        }

        public void Dispose()
        {
            Log.Info("MockDB.Dispose() called");
        }

        #endregion

        #region "Methods to add mock values to queues"

        public void AddCountResult(int countResult)
        {
            _countResult = _countResult ?? new Queue<int>();
            _countResult.Enqueue(countResult);
        }

        public void AddExpectedSaveCollectionResult<T>(SdbCollection<T> saveCollectionResult)
            where T : class, new()
        {
            _saveCollectionResult = _saveCollectionResult ?? new Queue<object>();
            _saveCollectionResult.Enqueue(saveCollectionResult);
        }

        public void AddExpectedSaveEntityResult(SdbEntity saveEntityResult)
        {
            _saveEntityResult = _saveEntityResult ?? new Queue<object>();
            _saveEntityResult.Enqueue(saveEntityResult);
        }

        public void AddLoadResult(SdbEntity loadResult)
        {
            _loadResult = _loadResult ?? new Queue<object>();
            _loadResult.Enqueue(loadResult);
        }

        public void AddQueryResult<T>(SdbCollection<T> queryResult) where T : class, new()
        {
            _queryResult = _queryResult ?? new Queue<object>();
            _queryResult.Enqueue(queryResult);
        }

        public void AddValueResult(string valueResult)
        {
            _valueResult = _valueResult ?? new Queue<string>();
            _valueResult.Enqueue(valueResult);
        }

        #endregion

        #region "Methods to get mock values from queues"

        private int GetCountResult()
        {
            try
            {
                int result = _countResult.Dequeue();
                return result;
            }
            catch (NullReferenceException)
            {
                throw new MockDbExceptions.MockDbNoMockDataException();
            }
        }

        private T GetLoadResult<T>() where T : class, new()
        {
            try
            {
                var result = (T)_loadResult.Dequeue();

                //if (result != null)
                //    result.db = this;

                return result;
            }
            catch (NullReferenceException)
            {
                throw new MockDbExceptions.MockDbNoMockDataException();
            }
        }

        private SdbCollection<T> GetQueryResult<T>() where T : class, new()
        {
            try
            {
                var result = (SdbCollection<T>)_queryResult.Dequeue();

                if (result != null)
                    result.Db = this;

                return result;
            }
            catch (NullReferenceException)
            {
                throw new MockDbExceptions.MockDbNoMockDataException();
            }
        }

        private string GetValueResult()
        {
            try
            {
                string result = _valueResult.Dequeue();
                return result;
            }
            catch (NullReferenceException)
            {
                throw new MockDbExceptions.MockDbNoMockDataException();
            }
        }

        #endregion
    }
}